#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
from ctypes import cdll, Structure, c_char_p, c_void_p, c_int, c_uint64, c_bool, byref, c_uint32, POINTER
from dataclasses import dataclass

from common.exception.common_exception import ErrCodeException
from common.util.scanner_utils import get_log_level

from ndmp.comm.log import log
from ndmp.comm.const import NDMPCode, Constant
from ndmp.schemas.ndmp_schemas import Resource


@dataclass
class AuthConfig:
    s_authkey: str = ""
    s_authpwd: str = ""
    s_ip: str = ""
    s_port: str = ""
    r_authkey: str = ""
    r_authpwd: str = ""
    r_ip: str = ""
    r_port: str = ""


class NdmpClientInterfaceS(Structure):
    _fields_ = [
        ("srcAuth", c_char_p),
        ("dstAuth", c_char_p),
        ("srcIp", c_char_p),
        ("dstIp", c_char_p),
        ("level", c_char_p),
        ("srcPwd", c_char_p),
        ("dstPwd", c_char_p),
        ("srcPath", c_char_p),
        ("dstPath", c_char_p),
        ("backFilePath", c_char_p),
        ("exclude", c_char_p),
        ("dumpType", c_int),  # 0: dorado , 1: NetApp
        ("port", c_int)
    ]


class FSInfoS(Structure):
    _fields_ = [
        ("invalid", c_uint32),
        ("fs_type", c_char_p),
        ("fs_logical_device", c_char_p),
        ("fs_physical_device", c_char_p),
        ("fs_status", c_char_p)
    ]


class NdmpFsInfoS(Structure):
    _fields_ = [
        ("error", c_int),
        ("fs_info_len", c_uint32),
        ("fs_info_val", POINTER(FSInfoS))
    ]


class NdmpStat(Structure):
    _fields_ = [
        ("status", c_int),
        ("process_bytes", c_uint64),
        ("remain_bytes", c_uint64),
        ("files_cnt", c_uint64)
    ]


class NDMPClient(object):
    def __init__(self, lib_path: str, client_log_path: str):
        self._client = cdll.LoadLibrary(lib_path)
        self._client_log_path = client_log_path

    def __del__(self):
        self.destroy()

    def destroy(self):
        self._client.NdmpDestroy()

    def init_log(self):
        log_level = get_log_level()
        self._client.InitLog.restype = c_void_p
        self._client.InitLog(self._client_log_path.encode(), log_level)

    def authentication(self, auth_conf: AuthConfig):
        authentication_conf = NdmpClientInterfaceS(auth_conf.s_authkey.encode(),
                                                   auth_conf.r_authkey.encode(),
                                                   auth_conf.s_ip.encode(),
                                                   auth_conf.r_ip.encode(),
                                                   0,
                                                   auth_conf.s_authpwd.encode(),
                                                   auth_conf.r_authpwd.encode(),
                                                   "".encode(),
                                                   "".encode(),
                                                   "".encode(),
                                                   "".encode(),
                                                   1,
                                                   int(auth_conf.s_port))
        self.init_log()
        self._client.NdmpClientAuth.restype = c_int
        ret = self._client.NdmpClientAuth(byref(authentication_conf))
        return ret

    def get_resource(self):
        retry_cnt = 0
        ret = -1
        self._client.NdmpGetFsInfo.argtypes = [POINTER(NdmpFsInfoS)]
        self._client.NdmpGetFsInfo.restype = c_int
        while retry_cnt < Constant.MAX_RETRY_TIMES and ret != 0:
            ndmp_fs_info = NdmpFsInfoS()
            ret = self._client.NdmpGetFsInfo(byref(ndmp_fs_info))
            retry_cnt += 1
        if ret != 0:
            log.error(f"NdmpGetFsInfo failed. Error code is {ret}.")
            raise ErrCodeException(ret, message="get_resource failed")
        fs_infos = []
        log.info(f"Get fs count: {ndmp_fs_info.fs_info_len}")
        fs_len = 0
        for i in range(ndmp_fs_info.fs_info_len):
            fs_info = Resource()
            fs_info.id = i
            fs_info.name = ndmp_fs_info.fs_info_val[i].fs_logical_device.decode()
            fs_info.extend_info = {"version": "v4", "protocol": "3", "isFs": "1"}
            status = ndmp_fs_info.fs_info_val[i].fs_status.decode()
            if status != "offline":
                fs_infos.append(fs_info)
                fs_len += 1
        return fs_len, fs_infos

    def backup(self, backup_conf: NdmpClientInterfaceS):
        log.info(f"Enter client backup")
        self.init_log()
        self._client.NdmpStartBackup.restype = c_int
        return self._client.NdmpStartBackup(byref(backup_conf))

    def restore(self, restore_info: NdmpClientInterfaceS):
        log.info(f"Enter client restore")
        self.init_log()
        self._client.NdmpStartRestore.restype = c_int
        return self._client.NdmpStartRestore(byref(restore_info))

    def abort(self, conf: NdmpClientInterfaceS):
        log.info(f"execute client abort job")
        self.init_log()
        self._client.NdmpAbortBackup.restype = c_int
        return self._client.NdmpAbortBackup(byref(conf))

    def get_task_info(self):
        log_level = get_log_level()
        self._client.InitLog.restype = c_void_p
        self._client.InitLog(self._client_log_path.encode(), log_level)
        self._client.NdmpGetStatFunc.restype = NdmpStat
        ndmp_status = self._client.NdmpGetStatFunc()
        return ndmp_status
